package com.gitee.wsl.time

import com.gitee.wsl.ext.number.format
import com.gitee.wsl.logger.LogLevel
import com.gitee.wsl.logger.Logger
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
import kotlin.time.TimeSource
import kotlin.time.TimedValue
import kotlin.time.measureTimedValue
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds



/**
 * Measures the average time taken by a given operation over a specified number of iterations.
 *
 * @param iterations The number of times the operation should be executed.
 * @param operation The operation to measure the execution time for, accepting the current iteration index as a parameter.
 * @return The average time taken by the operation in nanoseconds.
 */
inline fun TimeSource.measureAverageTime(iterations: Int, operation: (Int) -> Unit): Duration {
    var totalTime = 0.seconds
    var index = 0
    while (index < iterations) {
        val startTime = markNow()
        operation(index)
        totalTime += startTime.elapsedNow()
        index++
    }
    return totalTime / iterations
}

/**
 * Measures and prints the average execution time of a given operation over a specified number of iterations.
 *
 * @param iterations The number of times the operation should be executed.
 * @param text The label to display before the measured time.
 * @param operation The operation to measure, accepting the current iteration index as a parameter.
 */
inline fun TimeSource.printAverageExecTime(iterations: Int, text: String, operation: (Int) -> Unit) {
    val time = measureAverageTime(iterations, operation)
    val nanos = time.inWholeNanoseconds / 1_000_000.0
    println("$text: ${nanos.format(6)} ms")
}

/**
 * Measures and prints the average execution time of a given operation over a specified number of iterations with a default label.
 *
 * @param iterations The number of times the operation should be executed.
 * @param operation The operation to measure, accepting the current iteration index as a parameter.
 */
 inline fun TimeSource.printAverageExecTime(iterations: Int, operation: (Int) -> Unit) =
    printAverageExecTime(iterations, "Execution time", operation)

/**
 * Measures and prints the execution time of a given operation.
 *
 * @param T The return type of the operation.
 * @param text The label to display before the measured time.
 * @param operation The operation to measure the execution time for.
 * @return The result of the operation.
 */
 inline fun <T> TimeSource.printExecTime(text: String, operation: () -> T): T {
    val startTime = markNow()
    val r = operation()
    val elapsedTime = startTime.elapsedNow()
    val nanos = elapsedTime.inWholeNanoseconds / 1_000_000.0
    println("$text: ${nanos.format(6)} ms")
    return r
}

/**
 * Measures and prints the execution time of a given operation with a default label.
 *
 * @param T The return type of the operation.
 * @param operation The operation to measure the execution time for.
 * @return The result of the operation.
 */
inline fun <T> TimeSource.printExecTime(operation: () -> T) = printExecTime("Execution time", operation)

@OptIn(ExperimentalContracts::class)
inline fun <T> TimeSource.recordTimingImpl(
    log: (String) -> Unit,
    name: String,
    block: () -> T
): TimedValue<T> {

    contract {
        callsInPlace(block, kotlin.contracts.InvocationKind.EXACTLY_ONCE)
    }


    val data = measureTimedValue {
        block()
    }

    // Round to 2dp
    val ms = data.duration.inWholeMilliseconds.format(2)
    log("`$name` took `$ms` ms")
    return data
}


inline fun <T> Logger.recordTiming(
    source: TimeSource,
    name: String,
    level: LogLevel = LogLevel.INFO,
    block: () -> T
): TimedValue<T> = source.recordTimingImpl({ log(level, it) }, name, block)

